/*
 * @(#)DateTool.java 2011/5/11
 * 
 * Copyright 2010,2011 Ice Spring All Rights Reserved
 */
package com.mrogrape.io;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.URL;
import java.text.NumberFormat;

/**
 * 提供对元数据进行操作的各种实用方法。
 * 
 * 
 * @author  Ice Spring 
 * @version 1.0.0 2011/5/11
 */
public class DataUtils {


	private static final String MESSAGE_EXCEPTION_SERIALIZE_CANNOTCONTENT = "指定的长度无法储存序列化后的对象！";

	/**
	 * 将一个标记为可序列化的对象进行序列化操作。
	 * <p>
	 * 此方法通过{@link java.io.ObjectOutputStream}执行序列化操作并通过
	 * {@link java.io.ByteArrayOutputStream}获取序列化后的字节数组。
	 * 
	 * @param obj 被标记为可序列化的对象。
	 * @return 序列化后的字节数组。
	 */
	public static byte[] serialize(Serializable obj) {
		byte[] b = null;
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ObjectOutputStream oos = null;
		try {
			oos = new ObjectOutputStream(baos);
			oos.writeObject(obj);
			b = baos.toByteArray();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (oos != null) {
					oos.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return b;
	}

	/**
	 * 反序列化操作的实现。
	 * 
	 * 
	 * @param  bytes 通过{@link java.io.ObjectOutputStream}类执行序列化操作并通过
	 * 			 {@link java.io.ByteArrayOutputStream}生成的byte数组
	 * @return 反序列化后的对象
	 */
	public static Object deserialize(byte[] bytes) {
		ByteArrayInputStream bais = null;
		ObjectInputStream ois = null;
		Object o = null;

		try {
			bais = new ByteArrayInputStream(bytes);
			ois = new ObjectInputStream(bais);
			o = ois.readObject();

		} catch (Exception e) {
			e.printStackTrace();

		} finally {
			try {
				if (ois != null) {
					ois.close();
				}

			} catch (IOException e) {
				e.printStackTrace();

			}

		}
		return o;
	}

	/**
	 * 将一个可序列化的对象进行序列化操作，并输出到一个已知长度的byte数组中。
	 * <p>
	 * 指定的长度必须足够装下序列化后的对象。否则会抛出IllegalArgumentException.
	 * 
	 * @param obj 标记为可以序列化的对象。
	 * @param length 指定的长度
	 * @return 序列化得到的byte数组
	 */
	public static byte[] serialize(Serializable obj, int length) {
		byte[] b = null;
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ObjectOutputStream oos = null;
		try {
			oos = new ObjectOutputStream(baos);
			oos.writeObject(obj);
			b = baos.toByteArray();
			if (b.length <= length) {
				b = DataUtils.appendByteArraySize(b, length);
			} else {
				throw new IllegalArgumentException(DataUtils.MESSAGE_EXCEPTION_SERIALIZE_CANNOTCONTENT);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (oos != null) {
					oos.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return b;
	}

	/**
	 * 从输入流中反序列化一个Object对象。
	 * <p>
	 * 此方法采用java.io.ObjectInputStream来实现对象的反序列化。
	 * 
	 * @param is
	 *             输入流。
	 * @return 
	 * 		                  反序列化后得到的对象 如果EOF，返回null。  
	 * @throws ClassNotFoundException
	 *             如果类未找到。
	 * @throws IOException
	 *             如果发生输入输出异常。
	 */
	public static Object readObject(InputStream is)
	throws ClassNotFoundException, IOException {
		try {
			ObjectInputStream ois = new ObjectInputStream(is);
			return ois.readObject();
		} catch (EOFException e) {
			return null;
		} catch (IOException e) {
			throw e;
		} catch (ClassNotFoundException e) {
			throw e;
		}
	}

	/**
	 * 将可以序列化的对象写入输出流。
	 * 
	 * <p>
	 * 该方法采用{@link java.io.ObjectOutputStream}来实现对象的序列化发送.
	 * 
	 * @param os
	 *            需要写入对象的输出流。
	 * @param obj
	 *            可以序列化的对象，该对象必须实现java.io.Serializedable接口。
	 * @throws IOException 如果发生输入输出错误。
	 */
	public static void writeObject(OutputStream os, Serializable obj)
	throws IOException {
		try {
			ObjectOutputStream oos = new ObjectOutputStream(os);
			oos.writeObject(obj);
		} catch (IOException e) {
			throw e;
		}

	}

	/**
	 * 将一个byte数组添加至指定长度,空余长度以0填充。
	 * 
	 * @param b 原数组
	 * @param newSize 目标长度
	 * @return 扩容后的数组
	 */
	public static byte[] appendByteArraySize(byte[] b, int newSize) {
		if (newSize < b.length) {
			throw new IllegalArgumentException("新的数组长度不能小于原长度");
		}
		byte[] re = new byte[newSize];
		for (int i = 0; i < b.length; i++) {
			re[i] = b[i];
		}
		return re;
	}

	/**
	 * 将URL指向的文件对象复制到目标文件所映射的文件对象。
	 * 
	 * @param src 指向一个文件的URL
	 * @param output 目标文件
	 * 
	 * @throws FileNotFoundException 如果文件没有找到
	 * @throws IOException 如果复制过程中发生IO异常
	 */
	public static void copyFile(URL src, File output) throws FileNotFoundException,
	IOException {

		// Prepare the I/O stream
		BufferedInputStream bis = new BufferedInputStream(src.openStream());
		BufferedOutputStream bos = new BufferedOutputStream(
				new FileOutputStream(output));

		// Read and Write
		int read = 0;
		byte[] buff = new byte[1024];
		while ((read = bis.read(buff)) != -1) {
			bos.write(buff, 0, read);
		}

		// Close stream
		bos.close();
		bis.close();
	}

	/**
	 * 复制文件。
	 * 
	 * <p>
	 * 此方法仅提供最基础的文件复制功能，推荐用于小型资源文件的快速复制。此方法在复制
	 * 完成前会一直阻塞。 
	 * 
	 * @param input 源文件
	 * @param output 目标文件
	 * 
	 * @throws FileNotFoundException 如果文件没有找到
	 * @throws IOException 如果复制过程中发生IO异常
	 */
	public static void copyFile(File input, File output)
	throws FileNotFoundException, IOException {

		DataUtils.copyFile0(new FileInputStream(input), new FileOutputStream(output));
	}

	/**
	 * 复制文件的方法。
	 * 
	 * <p>
	 * 此方法仅提供最基础的文件复制功能，推荐用于小型资源文件的快速复制。此方法在复制
	 * 完成前会一直阻塞。 
	 * 
	 * @param is 复制源。
	 * @param os 复制目标。
	 * @throws IOException 如果发生IO异常。
	 */
	private static void copyFile0(InputStream is, OutputStream os)
	throws IOException {

		// 准备输入输出流。
		BufferedInputStream bis = new BufferedInputStream(is, DataUtils.BUF_SIZE);
		BufferedOutputStream bos = new BufferedOutputStream(os, DataUtils.BUF_SIZE);

		// 执行拷贝。
		int read = 0;
		byte[] buff = new byte[DataUtils.BUF_SIZE];
		while ((read = bis.read(buff)) != -1) {
			bos.write(buff, 0, read);
		}

		// 关闭流，释放资源。
		bos.close();
		bis.close();
	}

	// 复制文件时使用的缓冲区大小。
	private static final int BUF_SIZE = 1024;

	/**
	 * 描述字节数量。
	 * 
	 * <p>
	 * 以字符串的形式返回对字节数量的描述。类似于“1MB”、“1.23GB”这样的格式。这种描述
	 * 更多是为了阅读而非精确的记录。
	 * 
	 * <p>
	 * 描述基于以下规则。精确到小数点后2位。
	 * <ul>
	 * <li>长度小于1KB时直接以B为单位描述。
	 * <li>否则长度小于1MB时以KB单位描述。
	 * <li>否则长度小于1GB时以GB为单位描述。
	 * <li>大于1GB时以GB为单位描述。
	 * </ul>
	 * 
	 * @param bytes 字节数。
	 * @return 对字节数的描述。
	 */
	public static String descBytesLength(long bytes) {
		String re = null;

		NumberFormat nf = NumberFormat.getInstance();
		nf.setMaximumFractionDigits(2);
		nf.setMinimumFractionDigits(0);

		if (bytes < 1024) {
			re = bytes + DataUtils.UNIT_BYTE;
		} else if (bytes < 1048576) {
			double d = (double) bytes / 1024;
			re = nf.format(d) + DataUtils.UNIT_KB;
		} else if (bytes < 1073741824) {
			double d = (double) bytes / 1048576;
			re = nf.format(d) + DataUtils.UNIT_MB;
		} else {
			double d = (double) bytes / 1073741824;
			re = nf.format(d) + DataUtils.UNIT_GB;
		}
		return re;
	}

	// 使用的储存单位。
	private static final String UNIT_BYTE = "B";
	private static final String UNIT_KB = "KB";
	private static final String UNIT_MB = "MB";
	private static final String UNIT_GB = "GB";

}
